﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Emil.GMP;


namespace BYU_ECC
{

    class Point
    {
        public BigInt x;
		public BigInt y;
        
        #region Infinity Code
        public static Point Infinity = new Point();
        private bool IsInfinity = false;
        private Point()
        {
            IsInfinity = true;
        }
        #endregion


		public Point(BigInt _x, BigInt _y)
        {
            x = _x;
            y = _y;
        }

		public override bool Equals(object obj) {
			if (obj.GetType().Equals(typeof(Point)))
				return ((Point)obj).x == this.x && ((Point)obj).y == this.y;
			return false;
		}

		public override int GetHashCode() {
			return x.GetHashCode()+y.GetHashCode();
		}

        public override string ToString()
        {
            if (IsInfinity)
                return "(Infinity)";
            else
                return String.Format("({0},{1})", x, y);
        }

        public static bool operator !=(Point p1, Point p2)
        {
            return !(p1 == p2);
        }

        public static bool operator ==(Point p1, Point p2)
        {
            return (p1.IsInfinity && p2.IsInfinity) || ((p1.x == p2.x) && (p1.y == p2.y));
        }

        /// <summary>
        /// Point Addition operator
        /// </summary>
        /// <param name="p1"></param>
        /// <param name="p2"></param>
        /// <returns></returns>
        public static Point operator +(Point p1, Point p2)
        {
            if (p1.IsInfinity && p2.IsInfinity) return Point.Infinity;
            if (p1.IsInfinity) return p2;
            if (p2.IsInfinity) return p1;
            if (p1.x == p2.x && p1.y != p2.y) return Point.Infinity;
            if (p1.x != p2.x)
            {
 				BigInt lambda = (p2.y - p1.y).DivideMod(p2.x - p1.x, Curve.Mod);
				BigInt x3 = (lambda.Power(2) - p1.x - p2.x).Mod(Curve.Mod);
                if (x3 < 0) x3 = x3.Mod(Curve.Mod);
				BigInt y3 = (lambda * (p1.x - x3) - p1.y).Mod(Curve.Mod);
                if (y3 < 0) y3 = y3.Mod(Curve.Mod);
                return new Point(x3, y3);             
            }
            if (p1 == p2 && p1.y != 0)
            {
				BigInt lambda = (3 * p1.x.Power(2) + Curve.A).DivideMod(2 * p1.y, Curve.Mod);
				BigInt x3 = (lambda.Power(2) - 2 * p1.x).Mod(Curve.Mod);
                if (x3 < 0) x3 = x3.Mod(Curve.Mod);
				BigInt y3 = (lambda * (p1.x - x3) - p1.y).Mod(Curve.Mod);
                if (y3 < 0) y3 = y3.Mod(Curve.Mod);
                return new Point(x3, y3);
            }
            throw new Exception(String.Format("Unable to add points {0} and {1}",p1, p2));
        }

        /// <summary>
        /// Simple multiplication for normal ints.
        /// </summary>
        /// <param name="num"></param>
        /// <param name="p1"></param>
        /// <returns></returns>
        public static Point operator *(int num, Point p1)
        {
            if (num <= 0) throw new Exception("Can not multiply than any number less that 1");

            Point p = p1;
            for (int i = 2; i <= num; i++)
            {
                p = p + p1;
            }
            return p;   
        }

		/// <summary>
		/// Double-and-add algorithm to multiply point by an integer.
		/// </summary>
		/// <param name="k">BigInt representing the factor to multiply by</param>
		/// <param name="p1">Point to multiply</param>
		/// <returns>Point multiplied</returns>
		public static Point operator *(BigInt k, Point py) {
			if (k <= 0) throw new Exception("Can not multiply than any number less that 1");

			Boolean first = true;
			Point px = null;
			string kBinary = k.ToString(2);
			int m = kBinary.Length;
			for (int i = m-1; i >= 0; i--) {
				if (kBinary[i] == '1') {
					if (first) {
						first = false;
						px = py;
					} else {
						px = px + py;
					}
				}
				py = py + py;
			}
			return px;
		}

        public static Point operator -(Point p1)
        {    
            return new Point(p1.x, p1.y.Negate());
		}

		public static Point operator -(Point p1, Point p2) {
			return p1 + (-p2);
		}

		/// <summary>
		/// Converts a point into strings of arbitrary length
		/// </summary>
		/// <returns>A string consisting of the characters represented by this Point</returns>
		public string GetString() {
			string ret = "";
			if (x.ToByteArray().Length > 0)
				ret += System.Text.Encoding.Default.GetString(x.ToByteArray());
			if (y.ToByteArray().Length > 0)
				ret += System.Text.Encoding.Default.GetString(y.ToByteArray());
			return ret;
		}

		/// <summary>
		/// Converts a string into a point by splitting it in half and representing the text as bytes
		/// of a BigInt
		/// </summary>
		/// <param name="s">String to convert to a Point</param>
		/// <returns>Point representing the string</returns>
		public static Point ConvertToPoint(string s) {
            // Account for string of size 1
            if (s.Length == 1) {
                return new Point(new BigInt(System.Text.Encoding.Default.GetBytes(s)), BigInt.Zero);
            }
			int len = s.Length / 2;

            // Account for zero len
			if (len==0)
				return new Point(BigInt.Zero, BigInt.Zero);

            // All other cases
			string s1 = s.Substring(0, len);
			string s2 = s.Substring(len);
			BigInt x = new BigInt(System.Text.Encoding.Default.GetBytes(s1));
            BigInt y = new BigInt(System.Text.Encoding.Default.GetBytes(s2));
			return new Point(x, y);
		}
    }

}
